package funcSless

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	"gitee.com/wchpub/fnsvr"
	"github.com/redis/go-redis/v9"
)

// 无效数据错误
var ErrBlockInvalidDataError = errors.New("block: invalid data")

//游戏数据,一个玩家可以拥有多份游戏数据，不同的游戏数据代表不同的玩法
//通常把游戏内容区分为主玩法和副玩法，登录就需要的数据放主玩法中，其它数据切割成不同的game进行存储

//用ver来判断redis的缓存是否有效，而不是用key是否存在。有可能某个函数在读档时key是存在的，写入的时候由于过期，导致key不存在，
//这个时候会导致redis上的缓存是一个不完整的数据，必须保证redis block的数据是全量的，系统才能工作正常

// 版本号key,必然存在
const GAME_DATA_REQUIRED_KEY = "required"
const GAME_DATA_VERSION_KEY = "ver"

type i_block interface {
	save(pipe *redis.Pipeliner) (isChanged bool, reErr error)
}

type Block[KEY_TYPE ~string] struct {
	//已缓存的key
	keyValue map[string]string

	//计算类型的值缓存
	computedValue map[string]string

	//计划预装的key
	preLoadKeys map[string]struct{}

	//脏标记
	dirtyKeys map[string]struct{}

	//角色id
	playerId string

	//块名
	blockName string

	//是否预装全部
	isPreLoadAll bool

	//数据是否已经全部准备就绪
	isAllDataReady bool
}

// 从redis装载一个计算类型的浮点值
func (m *Block[KEY_TYPE]) LoadComputedFloat64Value(field string, luaScript string, args ...any) (float64, error) {
	strv, err := m.LoadComputedStringValue(field, luaScript, args...)
	if err != nil {
		return 0, err
	}

	return fnsvr.StringToFloat64(strv)
}

// 从redis装载一个计算类型的json值
func (m *Block[KEY_TYPE]) LoadComputedJsonValue(
	outV any,
	field string, luaScript string, args ...any,
) error {
	v, err := m.LoadComputedStringValue(field, luaScript, args...)
	if err != nil {
		return err
	}

	return fnsvr.StringToJson(v, outV)
}

// 从redis装载一个计算类型的整数值
func (m *Block[KEY_TYPE]) GetILoadComputedInt64Valuen64(field string, luaScript string, args ...any) (int64, error) {
	strv, err := m.LoadComputedStringValue(field, luaScript, args...)
	if err != nil {
		return 0, err
	}

	return fnsvr.StringToInt64(strv)
}

// 从redis装载一个计算类型的字符串值
func (m *Block[KEY_TYPE]) LoadComputedStringValue(field string, luaScript string, args ...any) (string, error) {
	v, err := m._loadComputedStringValue(field, luaScript, args...)
	if err != nil {
		if err != ErrBlockInvalidDataError {
			return "", err
		}

		//创建redis缓存数据
		err = m.loadFromMongo()
		if err != nil {
			return "", err
		}

		//再次尝试执行
		v, err = m._loadComputedStringValue(field, luaScript, args...)
		if err != nil {
			return "", err
		}
	}

	return v, nil
}

// 从redis装载一个计算类型的值
// 优先从本地内存缓存读取
// 脚本返回值必须是 [ver,value]
// ver用于验证缓存是否有效，value必须是字符串，复杂类型可以用json
func (m *Block[KEY_TYPE]) _loadComputedStringValue(field string, luaScript string, args ...any) (string, error) {
	v, ok := m.computedValue[field]
	if ok {
		return v, nil
	}
	rdb := gFuncSlessEnv.Redis
	ctx := context.Background()
	redisKey := makeBlockRedisKey(m.playerId, m.blockName)
	result := rdb.Eval(ctx, luaScript, []string{redisKey}, args...)
	err := result.Err()
	if err != nil {
		return "", err
	}

	retArr, err := result.StringSlice()
	if err != nil {
		return "", err
	}

	if len(retArr) != 2 {
		return "", fmt.Errorf("LoadComputedStringValue two parameters must be returned block:%s field:%s", m.blockName, field)
	}

	ver := retArr[0]
	if ver == "" {
		return "", ErrBlockInvalidDataError
	}

	v = retArr[1]

	//建立数据缓存
	m.computedValue[field] = v

	return v, nil
}

// 丢弃一个计算值，丢弃后会如果调用LoadComputedValue会被重新计算
func (m *Block[KEY_TYPE]) DiscardComputedValue(field string) {
	delete(m.computedValue, field)
}

// 预装key
func (m *Block[KEY_TYPE]) PreLoad(keys ...KEY_TYPE) {

	for _, k := range keys {
		_, ok := m.keyValue[string(k)]
		if ok {
			continue //已装载的key,跳过预装
		}

		if m.isAllDataReady {
			//数据已经全部在内存中,直接赋空值
			m.keyValue[string(k)] = ""

		} else {

			if string(k) != GAME_DATA_REQUIRED_KEY { //预装必要信息，因此无需预装
				m.preLoadKeys[string(k)] = struct{}{}
			}
		}

	}
}

// 获取block中的全部数据
func (m *Block[KEY_TYPE]) GetAll() (map[string]string, error) {
	m.preLoadAll()
	err := m.load()
	if err != nil {
		return nil, err
	}

	cloned := make(map[string]string)
	for k, v := range m.keyValue {
		cloned[k] = v
	}
	return cloned, nil
}

// 获取字符串数据
func (m *Block[KEY_TYPE]) GetString(key KEY_TYPE) (string, error) {

	err := m.load()
	if err != nil {
		return "", err
	}

	v, ok := m.keyValue[string(key)]
	if !ok {

		if m.isAllDataReady { //全部数据已经读取到内存了
			return "", nil
		}

		//禁止对未装入的key进行读取
		return "", fmt.Errorf("not found key:%s", key)
	}

	return v, nil
}

// 设置字符串数据
func (m *Block[KEY_TYPE]) SetString(key KEY_TYPE, value string) {
	strKey := string(key)
	m.keyValue[strKey] = value
	m.dirtyKeys[strKey] = struct{}{}
}

// 从数据块中获取json数据
func (m *Block[KEY_TYPE]) GetJson(key KEY_TYPE, outV any) error {
	v, err := m.GetString(key)
	if err != nil {
		return err
	}

	return fnsvr.StringToJson(v, outV)
}

// 设置json数据
func (m *Block[KEY_TYPE]) SetJson(key KEY_TYPE, value any) error {
	vBytes, err := json.Marshal(value)
	if err != nil {
		return err
	}
	m.SetString(key, string(vBytes))
	return nil
}

// 获取长整数类型
func (m *Block[KEY_TYPE]) GetInt64(key KEY_TYPE) (int64, error) {
	strv, err := m.GetString(key)
	if err != nil {
		return 0, err
	}

	return fnsvr.StringToInt64(strv)
}

// 设置长整数类型
func (m *Block[KEY_TYPE]) SetInt64(key KEY_TYPE, v int64) {
	strv := strconv.FormatInt(v, 10)
	m.SetString(key, strv)
}

// 获取整数类型
func (m *Block[KEY_TYPE]) GetInt(key KEY_TYPE) (int, error) {
	strv, err := m.GetString(key)
	if err != nil {
		return 0, err
	}

	return fnsvr.StringToInt(strv)
}

// 设置整数类型
func (m *Block[KEY_TYPE]) SetInt(key KEY_TYPE, v int) {
	strv := strconv.Itoa(v)
	m.SetString(key, strv)
}

// 获取布尔类型
func (m *Block[KEY_TYPE]) GetBool(key KEY_TYPE) (bool, error) {
	strv, err := m.GetString(key)
	if err != nil {
		return false, err
	}

	return strv == "1", nil
}

// 设置布尔类型
func (m *Block[KEY_TYPE]) SetBool(key KEY_TYPE, v bool) {
	var strv string

	if v {
		strv = "1"
	} else {
		strv = ""
	}
	m.SetString(key, strv)
}

// 获取浮点类型
func (m *Block[KEY_TYPE]) GetFloat64(key KEY_TYPE) (float64, error) {
	strv, err := m.GetString(key)
	if err != nil {
		return 0, err
	}

	return fnsvr.StringToFloat64(strv)
}

// 设置浮点类型
func (m *Block[KEY_TYPE]) SetFloat64(key KEY_TYPE, v float64) {
	strv := strconv.FormatFloat(v, 'f', 5, 64)
	m.SetString(key, strv)
}

// 预装全部，通常用于读档
func (m *Block[KEY_TYPE]) preLoadAll() {
	if m.isAllDataReady {
		return
	}
	m.isPreLoadAll = true
}

// 增量存档,返回true表示档案有变化
func (m *Block[KEY_TYPE]) save(pipe *redis.Pipeliner) (isChanged bool, reErr error) {
	if len(m.dirtyKeys) <= 0 {
		return false, nil
	}

	//如果没有预装任何值，如果直接写入redis可能会出现不完整数据,因此尝试装入数据
	err := m.load()
	if err != nil {
		return false, err
	}

	saveKeyValue := make(map[string]any)
	for k, _ := range m.dirtyKeys {
		saveKeyValue[k] = m.keyValue[k]
	}

	redisKey := makeBlockRedisKey(m.playerId, m.blockName)
	ctx := context.Background()

	//先设置一下过期时间，避免HSet后还没来得及设置新的过期时间，值就消失了
	(*pipe).Expire(ctx, redisKey, gFuncSlessEnv.DataExpireTime)
	(*pipe).HSet(ctx, redisKey, saveKeyValue)

	//再补一刀，上一个设置过期时间如果key不存在会失败！
	(*pipe).Expire(ctx, redisKey, gFuncSlessEnv.DataExpireTime)

	//清空脏数据
	m.dirtyKeys = make(map[string]struct{})

	return true, nil
}

// 从mongodb读取数据，并建立redis缓存
func (m *Block[KEY_TYPE]) loadFromMongo() error {
	//从mongo读取数据
	mongoCollection := makeBlockMongoCollection(m.blockName)
	mongoKey, err := fnsvr.GenerateMongoDbId(m.playerId)
	if err != nil {
		return err
	}

	mongoData := make(map[string]any)
	err = gFuncSlessEnv.Mongo.FindOne(mongoCollection, "_id", mongoKey, &mongoData)
	if err != nil {
		if err != fnsvr.ErrMongoNoDocuments {
			return err
		}

		//档案不存在时，放入缺省的游戏版本号
		mongoData["_id"] = mongoKey

		mongoData[GAME_DATA_REQUIRED_KEY] = "1" //固定不变，不允许更改
		mongoData[GAME_DATA_VERSION_KEY] = strconv.FormatInt(gFuncSlessEnv.DefaultVer, 10)

		//立即存入mongodb
		_, err = gFuncSlessEnv.Mongo.InsertOne(mongoCollection, mongoData)
		if err != nil {
			return err
		}
	}

	//删除无用的id数据
	delete(mongoData, "_id")

	redisKey := makeBlockRedisKey(m.playerId, m.blockName)
	ctx := context.Background()

	//尝试从redis读取数据，如果有读到优先用redis的数据
	//有可能某个函数在读档时key是存在的，写入的时候由于过期，导致key不存在，
	//这个时候会导致redis缓存了一部分数据，如果直接用mongodb数据覆盖，会导致redis缓存中新增的变更被旧档案覆盖
	{
		allCacheData, err := gFuncSlessEnv.Redis.HGetAll(ctx, redisKey).Result()
		if err != nil {
			return err
		}
		for k, v := range allCacheData {
			mongoData[k] = v
		}
	}

	//将数据立即存入redis
	pipe := gFuncSlessEnv.Redis.Pipeline()
	pipe.HSet(ctx, redisKey, mongoData)
	pipe.Expire(ctx, redisKey, gFuncSlessEnv.DataExpireTime)

	// 执行并获取结果
	if _, err := pipe.Exec(ctx); err != nil {
		return err
	}

	//将mongo数据填充进keyvalue
	//遍历mongoData
	for k, v := range mongoData {
		m.loadOneKeyDoneFillCache(k, v)
	}

	return nil
}

// 执行预装key逻辑
func (m *Block[KEY_TYPE]) doPreLoadKeys() error {

	//数据放入needLoadKeys
	needLoadKeys := []string{GAME_DATA_REQUIRED_KEY}
	for k, _ := range m.preLoadKeys {
		needLoadKeys = append(needLoadKeys, k)
	}

	keysLen := len(needLoadKeys)

	redisVals := []any{}
	if !m.isAllDataReady {
		//内存中不是全量数据

		redisKey := makeBlockRedisKey(m.playerId, m.blockName)
		ctx := context.Background()

		vals, err := gFuncSlessEnv.Redis.HMGet(ctx, redisKey, needLoadKeys...).Result()
		if err != nil {
			return err
		}

		if vals[0] == nil { //必要数据在redis中不存在
			err = m.loadFromMongo()
			if err != nil {
				return err
			}
		}

		redisVals = vals
	} else {
		//内存中已经是全量数据

		//填充redisVals
		for i := 0; i < keysLen; i++ {
			redisVals = append(redisVals, nil)
		}
	}

	//值填充进缓存区域
	for i := 0; i < keysLen; i++ {
		currK := needLoadKeys[i]
		currV := redisVals[i]
		m.loadOneKeyDoneFillCache(currK, currV)
	}

	//清空preLoadKeys
	m.preLoadKeys = make(map[string]struct{})

	return nil
}

// 一个键值对装载成功后，填充进内存缓存
func (m *Block[KEY_TYPE]) loadOneKeyDoneFillCache(k string, v any) {
	_, exist := m.keyValue[k]
	if exist {
		return //数据已存在，优先用内存数据
	}
	if v == nil {
		m.keyValue[k] = ""
	} else {
		s, ok := v.(string)
		if ok {
			m.keyValue[k] = s
		} else {
			m.keyValue[k] = ""
			fnsvr.Log().Warn("loadOneKeyDoneFillCache  key is not a string", "playerId", m.playerId, "blockName", m.blockName, "key", k, "value", v)
		}
	}
}

// 执行预装全部逻辑
func (m *Block[KEY_TYPE]) doPreLoadAll() error {
	redisKey := makeBlockRedisKey(m.playerId, m.blockName)
	ctx := context.Background()
	result, err := gFuncSlessEnv.Redis.HGetAll(ctx, redisKey).Result()
	if err != nil {
		return err
	}

	_, verExist := result[GAME_DATA_REQUIRED_KEY]
	if !verExist {
		err = m.loadFromMongo()
		if err != nil {
			return err
		}
	} else {
		//遍历result
		for k, v := range result {
			m.loadOneKeyDoneFillCache(k, v)
		}
	}

	m.isAllDataReady = true
	return nil
}

// 装载数据
func (m *Block[KEY_TYPE]) load() error {

	if m.isPreLoadAll && !m.isAllDataReady {
		//全量装载
		err := m.doPreLoadAll()
		if err != nil {
			return err
		}
	}

	_, verOk := m.keyValue[GAME_DATA_REQUIRED_KEY]

	//必须放在装载全量之后，否则io次数可能增多
	if len(m.preLoadKeys) > 0 || //存在需要预装的key
		!verOk { //缓存中不存在版本信息
		//根据设定的预装key进行装载
		return m.doPreLoadKeys()
	}

	return nil
}

// 创建一个游戏数据块实例
func newBlock[KEY_TYPE ~string](playerId string, blockName string) *Block[KEY_TYPE] {
	m := &Block[KEY_TYPE]{
		keyValue:      make(map[string]string),
		computedValue: make(map[string]string),
		preLoadKeys:   make(map[string]struct{}),
		dirtyKeys:     make(map[string]struct{}),
		playerId:      playerId,
		blockName:     blockName,
	}
	return m
}
